package net.caiyixiu.liaoji.net.retrofit.remoteCallAdapter

import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext

@Suppress("unused", "UNCHECKED_CAST")
abstract class CoroutineCall<T, E : Throwable, THIS : CoroutineCall<T, E, THIS>> {
    abstract fun cancel()
    protected abstract fun sync(): T
    protected abstract fun handleThrowable(t: Throwable): E
    private var onError: ((e: E) -> Unit)? = null
    private var scope: CoroutineScope? = null
    protected open val bgContext = Dispatchers.IO
    protected open val handleContext = Dispatchers.Main
    fun sync(onException: ((e: E) -> Unit)? = null): T? {
        return try {
            sync()
        } catch (e: Throwable) {
            onException?.invoke(handleThrowable(e))
            null
        }
    }

    open fun watch(scope: CoroutineScope): THIS {
        this.scope = scope
        return this as THIS
    }

    open fun onError(block: (e: E) -> Unit): THIS {
        this.onError = block
        return this as THIS
    }

    open fun syncOrThrow(): T {
        return sync()
    }

    open fun async(bgContext: CoroutineContext? = null, handleContext: CoroutineContext? = null, onException: ((e: E) -> Unit)? = onError, onSuccess: (data: T) -> Unit) {
        val async = async(bgContext)
        (scope ?: GlobalScope).launch(handleContext ?: this.handleContext) {
            try {
                onSuccess(async.await())
            } catch (e: CancellationException) {
            } catch (e: Exception) {
                val handleException = handleThrowable(e)
                onException?.invoke(handleException)
            }
        }
    }


    fun async(bgContext: CoroutineContext? = null): Deferred<T> {
        return (scope ?: GlobalScope).async(bgContext ?: this.bgContext) {
            sync()
        }
    }

    fun <NT> map(block: (T) -> NT): MapCoroutineCall<NT> {
        return MapCoroutineCall(block)
    }

    inner class MapCoroutineCall<NT>(val block: (T) -> NT) : CoroutineCall<NT, E, MapCoroutineCall<NT>>() {
        override fun cancel() {
            this@CoroutineCall.cancel()
        }

        override fun sync(): NT {
            val sync = this@CoroutineCall.sync()
            return block.invoke(sync)
        }

        override fun handleThrowable(t: Throwable): E {
            return this@CoroutineCall.handleThrowable(t)
        }
    }

}
